# Class for report generation
=begin
Copyright (C) 2014 Enrique Rando

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

http://www.gnu.org/licenses/gpl-2.0.html
=end


require File.expand_path(File.dirname(__FILE__) + '/Translator.rb')

require 'fileutils'
require 'cgi'

# Class for report generation
class Report_generator
	# Object creation
	# Parameters
	#	file	file path for the report
	#	overwrite 	whether file must be overwritten if it already exists
	#	lang	Language for the report
	def initialize(file, overwrite=false, lang='lang/spanish.txt')
		@translator = Translator.new(lang)

		# Drawing state
		@host_open = false
		@folder_open = false
		@file_open = false
		
		# Check if file exists
		if File.exists?(file) and not overwrite
			raise "File '#{file}' already exists"
		end
	
		# Create directory to contain file, if it doesn't exist
		file_dir = File.dirname(file)
		if not File.exists?(file_dir)
			FileUtils.mkdir_p(file_dir)
		end
		
		# Directory to store auxiliary report files
		@dir = file + ".dir"
		
		# Create directory for auxiliary files
		if File.exists?(@dir) 
			if not overwrite
				raise "Directory '#{@dir}' already exists"
			end
		else
			FileUtils.mkdir_p(@dir)
		end
		
		@hosts = []

		@file = File.open(file, 'w')
		@file.write(style_and_js)
	end
	
	# Begin the report content
	def begin(title)
		@file.write("<h1>#{CGI.escapeHTML(title)}</h1>\n#{translate(:generated_by)}<br>\n")
	end

	def end
		@file.close
	end

	def begin_test(title)
		@hosts = []
		@host_open = false
		@folder_open = false
		@file_open = false
		@counter = 0
		@host_counter = 0
		@folder_counter = 0
		@file.write("<h2>#{translate(:test)}: #{CGI.escapeHTML(title)}</h2>\n")
	end
	
	def end_test
		end_host
		@file.write("<br><h3>#{translate(:problems_found)}: #{@counter}<h3><hr><hr>")
		@file.write("<table>\n" + 
			"<tr><td>#{translate(:host)}</td><td>#{translate(:problems_found)}</td></tr>\n" +
			@hosts.sort{|x,y| x[:counter] <=> y[:counter]}.collect{|h| "<tr><td>#{h[:host]}</td><td>#{h[:counter]}</td></tr>"}.join("\n")  +
			"</table>\n") if @hosts.size>0
	end
	
	def begin_host(host)
		# Problem counter
		@host_counter = 0
		@current_host = host
		
		@host_open = true
		@file.write("<table class='host'><tr>\n<td class='hostname' colspan='2'>#{translate(:host)}: #{CGI.escapeHTML(host)}<button onclick='toggle(this)'>#{translate(:hide_data)}</button></td></tr>\n<tr><td class='gap'>&nbsp</td><td class='hostdata'>\n")
	end
	
	def end_host
		if @host_open
			end_folder
			@file.write("</td></tr><tr><td colspan='2'><br><h4>#{translate(:number_of_problems_for_host)}: #{@host_counter}<h4></td></tr></table><br>\n")
			@hosts << {:host=>@current_host, :counter=>@host_counter}
		end
		@host_open = false
	end
	
	def begin_folder(folder)
		# Problem counter
		@folder_counter = 0
		
		@folder_open = true
		@file.write("<table class='folder'><tr>\n<td class='foldername' colspan='2'>#{translate(:folder)}: #{CGI.escapeHTML(folder)}<button onclick='toggle(this)'>#{translate(:hide_data)}</button></td></tr>\n<tr><td class='gap'>&nbsp</td><td class='folderdata'>\n")
	end
	
	def end_folder
		if @folder_open
			end_file
			@file.write("</td></tr><tr><td colspan='2'><h4><br>#{translate(:number_of_problems_for_folder)}: #{@folder_counter}<h4></td></tr></table>\n")
		end
		@folder_open = false
	end
	
	def begin_file(file)
		@file.write("<table class='file'><tr><td class='filename' colspan='2'>#{translate(:file)}: #{CGI.escapeHTML(file)}<button onclick='toggle(this)'>#{translate(:hide_data)}</button></td></tr>\n<tr><td class='gap'>&nbsp</td><td class='filedata'>\n")
		@file_open = true
	end
	
	def end_file
		if @file_open
			@file.write("</td></tr></table>\n")
		end
		@file_open = false
	end
	
	def not_tested(url, bsd_result, text, params={})
		display_url_data(url, bsd_result, translate(text, params))
	end

	# List problems for URL
	# Parameters:
	#	bsd_result		the search result
	#	analysis	the data generated by the analysis
	#	to_highlight	text to highlight
	def problem(url, bsd_result, analysis, to_highlight)
		@counter += 1
		@host_counter += 1
		@folder_counter += 1
		
		# List analysis data
		text = "<table class='problem_list'>"
		analysis.each do |name, data|
			data[:description].each do |item|
				if item[:info].is_a?(Array)
					text += "<tr><td class='problem_list'><table class='detaildata'>"
					text += "<tr><td class='sep_space'>&nbsp;&nbsp;</td>" + 
							item[:headers].collect{|x| "<td class='label'>" + translate(x, item[:parameters]) + "</td>"}.join("") + "</tr>" unless item[:headers].nil?
					item[:info].each_with_index do |row, line|
						td_class = item[:level][line].to_s + "_space"
						text += "<tr><td class='#{td_class}'>&nbsp;&nbsp;</td>" + 
							row.collect{|x| "<td class='detaildata'>" + x + "</td>"}.join("") + "</tr>" 
					end
					text += "</table></td></tr>"
				else
					td_class = item[:level].to_s 
					text += "<tr><td class='#{td_class}'>#{translate(item[:info], item[:parameters])}</td></tr>\n"
				end
			end
			text += "<tr><td>&nbsp;</td></tr>"
		end
		text += "</table><br><br>"
		
		
		# Generate detail data
		detail_file = "#{CGI.escapeHTML(@dir)}/#{@counter}.html"
		detail_file_relative = "#{CGI.escapeHTML(File.basename(@dir))}/#{@counter}.html"
		text += "<a href='#{CGI.escapeHTML(detail_file_relative)}' target='_blank'><div class='link'>#{translate(:more_details)}</div></a>"
		
		generate_details(detail_file, url, analysis, to_highlight)
		
		# Generate report output
		display_url_data(url, bsd_result, text)
	end

	# Says that no problem was found for an URL
	# Parameters
	#	url			The URL
	#	bsd_result	bsd_result data structure with info about the URL
	def maybe_ok(url, bsd_result)
		# Create a table with result summary data
		rows = ""
		bsd_result.each do |name, data|
			if not data[:summary].nil?
				rows += "<tr><td class='label'>#{CGI.escapeHTML(name)}</td><td class='summaryrow'>#{CGI.escapeHTML(data[:summary])}</td></tr>\n"
			end
		end
		text = "#{translate(:no_mailicious_content)}<h4>#{translate(:summaries)}</h4>"
		text += (rows=="" ? translate(:no_summary) : "<table class='summaries'>#{rows}</table>")
		
		display_url_data(url, bsd_result, text) 
	end
private
	# Prepares a translation
	# Parameter d	data to translate
	def translate(d, params={})
		return @translator.translate(d, params)
	end

	# Info about a result
	# Parameters
	#	url		Result url
	#	bsd_result	the result
	#	text	info text
	def display_url_data(url, bsd_result, text)
		# Determine Search Engines providing the result
		search_engines = bsd_result.keys.join(", ")
		
		@file.write("
			<table class='result'>
			<tr><td class='label'>#{translate(:url)}</td><td class='url'>#{CGI.escapeHTML(url)}</td></tr>
			<tr><td class='label'>#{translate(:provided_by)}</td><td class='selist'>#{CGI.escapeHTML(search_engines)}</td></tr>
			<tr><td class='label'>#{translate(:info)}</td><td class='resultreport'>#{text}</td></tr>
			<tr><td>&nbsp;</td><td>&nbsp;</td></tr>
			</table>\n
		")
	end

	# Create a detail report file
	# Parameters
	#	detail_file	file to generate
	#	url		url for the result
	#	analysis	data generated in analysis
	#	to_highlight text to highlight
	def generate_details(detail_file, url, analysis, to_highlight)
		# Create the file
		file = File.open(detail_file, "w")
		file.write(style_and_js)
		
		file.write("<h1>#{translate(:details_for)} #{url}</h1>")
		
		# Columns
		columns = analysis.keys
		
		# Control panel
		text = "<br><h2>#{translate(:hide_show_analysis)}</h2>"
		columns.each_with_index do |name, i|
			text += "<input type='checkbox' name='c#{i}' onclick='hide_show_column(this, \"d#{i}\", \"c#{i}\")' checked='1'>#{translate(name)}</checkbox><br>"
		end
		text += "<button onclick='table_fit()' style='float:left'>#{translate(:fit_to_width)}</button>"
		
		control_panel = text

		# Analysis Data
		text += "<br><h2>#{translate(:data)}</h2><table id='maintable' style='word-wrap: break-word;width:100%;table-layout:fixed'>"
		
		# Identification data row
		text += "<tr><td class='label'>&nbsp;</td>"
		columns.each_with_index do |name, i|
			text += "<td class='label' name='d#{i}'>#{translate(name)}</td>"
		end
		text += "</tr>"
		
		# Redirection info
		text += "<tr><td class='label'>#{translate(:redirection_problems)}?</td>"
		columns.each_with_index do |name, i|
			data = analysis[name]
			redir_class = data[:external_redirection] ? "alert" : (data[:redirection_to_authorized] ? "warning" : "ok")
			redir_text = data[:external_redirection] ? translate(:external_redirection) : (data[:redirection_to_authorized] ? translate(:redirection_to_authorized) : translate(:no_redirection))
			text += "<td class='#{redir_class}' name='d#{i}'>#{redir_text}</td>"		
		end
		text += "</tr>"
		
		# Text found info
		text += "<tr><td class='label'>#{translate(:suspicious_content)}</td>"
		columns.each_with_index do |name, i|
			data = analysis[name]
			text_found_class = data[:text_found] ? "alert" : "ok"
			text_found_text = data[:text_found] ? translate(:found) : translate(:not_found)
			text += "<td class='#{text_found_class}' name='d#{i}'>#{text_found_text}</td>"	
		end
		text += "</tr>"
		
		file.write(text)
		
		
		# Headers
		text = "<tr><td class='label'>#{translate(:http_headers)}<br>(#{translate(:last_if_redirs)})</td>"
		columns.each_with_index do |name, i|
			data = analysis[name]
			headers = data[:response].nil? ? "&nbsp;" : data[:response].each_header.collect{|x,y| CGI.escapeHTML(x) + ": " + CGI.escapeHTML(y)}.join("<br>")
			text += "<td class='detaildata' name='d#{i}'>#{headers}</td>"
		end
		text += "</tr>"
		file.write(text)
		
		# HTML response
		text = "<tr><td class='label'>#{translate(:html_response)}<br>(#{translate(:last_if_redirs)})</td>"
		columns.each_with_index do |name, i|
			data = analysis[name]

			if data[:response].nil?
				body = "<table class='detaildata'>"
				data[:description].each do |item|
					if item[:info].is_a?(Array)
						body += "<tr><td class='problem_list'><table class='detaildata'>"
						body += "<tr><td class='sep_space'>&nbsp;&nbsp;</td>" + 
								item[:headers].collect{|x| "<td class='label'>" + translate(x, item[:parameters]) + "</td>"}.join("") + "</tr>" unless item[:headers].nil?
						item[:info].each_with_index do |row, line|
							td_class = item[:level][line].to_s + "_space"
							body += "<tr><td class='#{td_class}'>&nbsp;&nbsp;</td>" + 
								row.collect{|x| "<td class='detaildata'>" + highlight(translate(x, item[:parameters]), to_highlight) + "</td>"}.join("") + "</tr>" 
						end
						body += "</table></td></tr>"
					else
						td_class = item[:level].to_s 
						body += "<tr><td class='#{td_class}'}>#{highlight(translate(item[:info], item[:parameters]), to_highlight)}</td></tr>\n"
					end
				end
				body += "</table>"
			else
				body = highlight(CGI.escapeHTML(data[:response].body.to_s), to_highlight)
			end
			text += "<td class='detaildata' name='d#{i}'>#{body}</td>"

			file.write(text)
			text = ""
		end
		
		file.write("</tr></table><br><br>#{control_panel}")
		file.close
	end
	
	def highlight(str, to_highlight)
		result = str
		to_highlight.each do |t|
			result.gsub!(t, "<span class='highlight'>#{t}</span>")
		end
		
		return result
	end
	
	# STYLE and JAVASCRIPT for report file
	def style_and_js
		gap_width_pct = 3
		data_width_pct = 100 - gap_width_pct
	
		return "
			<style>
				.host {
					width: 100%;
					border: solid;
				}

				.hostname {
					width: 100%;
					border: solid;
					background-color: tan;
					color: black;
				}
				
				.gap {
					width: #{gap_width_pct}%
				}
				
				.hostdata {
					width: #{data_width_pct}%;
					border: solid
				}

				.folder {
					width: 100%;
					border: solid;
				}

				.foldername {
					width: 100%;
					border: solid;
					background-color: lightblue;
					color: black;
				}

				
				.folderdata {
					width: #{data_width_pct}%;
					border: solid
				}

				.file {
					width: 100%;
					border: solid;
				}

				.filename {
					width: 100%;
					border: solid;
					background-color: wheat;
					color: black;
				}

				
				.filedata {
					width: #{data_width_pct}%;
					border: solid
				}

				.url {
					width: 100%;
					border: solid;
				}
				
				.problem_list {
					vertical-align: top;
					width: 100%;
					border: none;
				}
				
				.section {
					vertical-align: top;
					width: 100%;
					color: white;
					background-color: blue;
				}
				
				.warning {
					vertical-align: top;
					width: 100%;
					background-color: yellow;
				}
				
				.alert {
					vertical-align: top;
					width: 100%;
					background-color: tomato;
				}
				
				.ok {
					vertical-align: top;
					width: 100%;
					background-color: lightgreen;
				}
				
				.info {
					vertical-align: top;
					width: 100%;
				}
				
				.warning_space {
					vertical-align: top;
					background-color: yellow;
				}
				
				.alert_space {
					vertical-align: top;
					background-color: tomato;
				}
				
				.ok_space {
					vertical-align: top;
					background-color: lightgreen;
				}

				.sep_space {
					vertical-align: top;
				}
				
				.info_space {
					vertical-align: top;
				}

				
				.link {
					text-decoration: none;
					font-size: 140%;
					background-color: orange;
					text-align: center;
					color: black;
					margin-left: auto;
					margin-right: auto;
					width: 30%;
				}
				
				a {
					text-decoration:none;
					color:black
				}
				
				.result {
					vertical-align: top;
					width: 100%;
					border: solid;
				}
			
				.label {
					vertical-align: top;
					background-color: lightgray;
					border: solid;
				}
								
				.selist {
					vertical-align: top;
					width: 100%;
					border: solid;
				}
				
				.resultreport {
					vertical-align: top;
					width: 100%;
					border: solid;
				}
				
				.detaildata {
					vertical-align: top;
					border: solid;
				}
				
				.summaries {
					vertical-align: top;
				}

				.summaryrow {
					vertical-align: top;
					width: 100%;
					border: solid;
				}
				
				button {
					float: right;
					font-size: 140%;
				}
				
				.highlight {
					background-color: brown;
					color: white;
				}
				</style>
		
			<script>
				function hide_show_column(checkbox, col_name, ch_name) {
					checked = checkbox.checked
					
					elements = document.getElementsByName(col_name)
					size = elements.length;
					for (i=0;i<size;i++) {
						elements.item(i).style.display = (checked ? '' : 'none');
					}
					
					elements = document.getElementsByName(ch_name)
					size = elements.length;
					for (i=0;i<size;i++) {
						elements.item(i).checked = checked;
					}
				}										
				
				function toggle(button) { 
					element = button;
					while (element.tagName.toUpperCase() != 'TABLE') {
						element = element.parentElement;
					}
					
					trs = element.rows;
					disp = trs[1].style.display; 

					trs[1].style.display = disp=='none' ? '' : 'none';		
					button.innerHTML = disp=='none' ? '#{translate(:hide_data)}' : '#{translate(:show_data)}';
					
				}
				
				function table_fit() {
					table = document.getElementById('maintable');
					
					table.style.tableLayout = (table.style.tableLayout == 'fixed') ? '' : 'fixed';
				}
			</script>
		"
	end
end